//=============================================================================
// GraphCamera.cpp by Frank Luna (C) 2004 All Rights Reserved.
// Adapted for DAE GP1 by Bart Uyttenhove
//=============================================================================
#include "StdAfx.h"
#include "GraphCamera.h"
#include "InputState.h"
#include "d3dUtil.h"
#include <boost/foreach.hpp>

GraphCamera::GraphCamera(int clientWidth, int clientHeight)
:LevelElement()
,m_Running(10.0f)
,m_Speed(5.0f)
,m_MouseScale(100)
,m_FOV(3.1415f/4.0f)
,m_AspectRatio(4.0f/3)
, m_NearClippingPlane(1.0f)
, m_FarClippingPlane(1000.0f)
,m_bIsActive(1)
,m_ClientWidth(clientWidth)
,m_ClientHeight(clientHeight)
,IsOrthoGraph(false)
,m_GraphCamera(0)
,m_bCropMatrix(false)
{
	D3DXMatrixIdentity(&m_matView);
	D3DXMatrixIdentity(&m_matProj);
	D3DXMatrixIdentity(&m_matViewProj);

	m_PosW   = D3DXVECTOR3(0.0f, 7.0f,-18.0f);
	m_RightW = D3DXVECTOR3(1.0f, 0.0f, 0.0f);
	m_UpW    = D3DXVECTOR3(0.0f, 1.0f, 0.0f);
	m_LookW  = D3DXVECTOR3(0.0f, -0.4f, 1.0f );

	// Client should adjust to a value that makes sense for application's
	// unit scale, and the object the camera is attached to--e.g., car, jet,
	// human walking, etc.

	//Use window client window dimensions to calc new aspect ratio
	float Aspect = (float)clientWidth / clientHeight;
	SetAspectRatio(Aspect);
}
GraphCamera::GraphCamera(int clientWidth, int clientHeight, float nearplane, float farplane)
:LevelElement()
,m_Running(10.0f)
,m_Speed(5.0f)
,m_MouseScale(100)
,m_FOV(3.1415f/4.0f)
,m_AspectRatio(4.0f/3)
, m_NearClippingPlane(nearplane)
, m_FarClippingPlane(farplane)
,m_bIsActive(1)
,m_ClientWidth(clientWidth)
,m_ClientHeight(clientHeight)
,IsOrthoGraph(false)
{
	D3DXMatrixIdentity(&m_matView);
	D3DXMatrixIdentity(&m_matProj);
	D3DXMatrixIdentity(&m_matViewProj);

	m_PosW   = D3DXVECTOR3(0.0f, 7.0f,-18.0f);
	m_RightW = D3DXVECTOR3(1.0f, 0.0f, 0.0f);
	m_UpW    = D3DXVECTOR3(0.0f, 1.0f, 0.0f);
	m_LookW  = D3DXVECTOR3(0.0f, -0.4f, 1.0f );

	// Client should adjust to a value that makes sense for application's
	// unit scale, and the object the camera is attached to--e.g., car, jet,
	// human walking, etc.

	//Use window client window dimensions to calc new aspect ratio
	float Aspect = (float)clientWidth / clientHeight;
	SetAspectRatio(Aspect);
}
GraphCamera::~GraphCamera()
{
}

const D3DXMATRIX& GraphCamera::GetView() const
{
	return m_matView;
}

const D3DXMATRIX& GraphCamera::GetProj() const
{
	return m_matProj;
}
const D3DXMATRIX& GraphCamera::GetViewProj() const
{
	return m_matViewProj;
}

const D3DXVECTOR3& GraphCamera::GetRight() const
{
	return m_RightW;
}

const D3DXVECTOR3& GraphCamera::GetUp() const
{
	return m_UpW;
}

const D3DXVECTOR3& GraphCamera::GetLook() const
{
	return m_LookW;
}

const D3DXVECTOR3& GraphCamera::GetPos()const
{
	return m_PosW;
}

/**
* Create the camera with the specified position , target and up vector.
* This method calls the buildView method internally.
* @param pos the position for the camera.
* @param target the target for the camera.
* @param up the up vector for the camera.
*/
void GraphCamera::LookAt(const D3DXVECTOR3& pos, const D3DXVECTOR3& target,const  D3DXVECTOR3& up)
{
	D3DXVECTOR3 Lookat = target - pos;
	D3DXVec3Normalize(&Lookat, &Lookat);

	D3DXVECTOR3 Right;
	D3DXVec3Cross(&Right, &up, &Lookat);
	D3DXVec3Normalize(&Right, &Right);

	D3DXVECTOR3 Up;
	D3DXVec3Cross(&Up, &Lookat, &Right);
	D3DXVec3Normalize(&Up, &Up);

	m_PosW   = pos;
	m_RightW = Right;
	m_UpW    = Up;
	m_LookW  = Lookat;

	BuildViewMatrix();
	//if(IsOrthoGraph)
	//{
	//	m_matViewProj = m_matView * m_matProj * CalculateCropMatrix();
	//} else {
		m_matViewProj = m_matView * m_matProj;
	//}
}

void GraphCamera::Rotate(D3DXVECTOR3 angles)
{
	D3DXMATRIXA16 rotmat;
	D3DXMatrixRotationYawPitchRoll(&rotmat,angles.y,angles.x,angles.z);
	D3DXVECTOR4 helper;
	D3DXVec3Transform(&helper,&m_RightW,&rotmat);
	m_RightW = D3DXVECTOR3(helper.x,helper.y,helper.z);

	D3DXVec3Transform(&helper,&m_UpW,&rotmat);
	m_UpW = D3DXVECTOR3(helper.x,helper.y,helper.z);

	D3DXVec3Transform(&helper,&m_LookW,&rotmat);
	m_LookW = D3DXVECTOR3(helper.x,helper.y,helper.z);
	
	D3DXVec3Normalize(&m_RightW, &m_RightW);
	D3DXVec3Normalize(&m_UpW, &m_UpW);
	D3DXVec3Normalize(&m_LookW, &m_LookW);

	BuildViewMatrix();
	//if(IsOrthoGraph)
	//{
	//	m_matViewProj = m_matView * m_matProj * CalculateCropMatrix();
	//} else {
		m_matViewProj = m_matView * m_matProj;
	//}
}

/**
* Sets the perspective parameters for this camera.
* @param fov the field of view for this camera.
* @param aspect the aspect ratio (it is best to let this value correspond with the aspect
* ration of the viewport.
* @param nearZ the near clipping plane. 
* @param farZ the far clipping plane.
*/
void GraphCamera::SetLens(float aspect, float fov, float nearZ, float farZ)
{
	m_FOV = fov;
	m_AspectRatio = aspect;
	m_NearClippingPlane = nearZ;
	m_FarClippingPlane = farZ;
	BuildProjectionMatrix();
}

void GraphCamera::SetAspectRatio(float Aspect)
{
	m_AspectRatio = Aspect;
	BuildProjectionMatrix();
}

/**
* Sets the speed of the camera.
* @param s the speed for this camera.
*/
void GraphCamera::SetSpeed(float s)
{
	m_Speed = s;
}

void GraphCamera::SetMouseScale(int scale)
{
	if(scale>1 && scale<1000)
		m_MouseScale = (float)scale;
}

void GraphCamera::SetPos(const D3DXVECTOR3& pos)
{
	m_PosW = pos;
	// Rebuild the view matrix to reflect changes.
	BuildViewMatrix();
	//if(IsOrthoGraph)
	//{
	//	m_matViewProj = m_matView * m_matProj * CalculateCropMatrix();
	//} else {
		m_matViewProj = m_matView * m_matProj;
	//}

}
/**
* Updates the camera ( for first person camera for example) 
* @param dt the delta time.
*/
void GraphCamera::Tick(const InputState & refInputState)
{
	//Move(D3DXVECTOR3(0, 0, 0), true, 0.000f, 0.0f);
	//BuildViewMatrix();
	if(m_bCropMatrix)
		m_matViewProj = m_matView * m_matProj * CalculateCropMatrix();
	if(!m_bIsActive)return;
}

void GraphCamera::ToggleCropMatrix()
{
	if(m_bCropMatrix)
		m_bCropMatrix = false;
	else
		m_bCropMatrix = true;
}
/**
* Construct the view matrix for this camera.
*/ 
void GraphCamera::BuildViewMatrix()
{
	D3DXVECTOR3 PosW,UpW,LookW,RightW;
	D3DXVECTOR4 Out;

	D3DXVec3Transform(&Out,&m_PosW,&m_World);
	PosW=D3DXVECTOR3(Out.x,Out.y,Out.z);
	//directional vectors are only rotated
	m_World._41=0;
	m_World._42=0;
	m_World._43=0;
	D3DXVec3Transform(&Out,&m_UpW,&m_World);
	UpW=D3DXVECTOR3(Out.x,Out.y,Out.z);
	D3DXVec3Transform(&Out,&m_LookW,&m_World);
	LookW=D3DXVECTOR3(Out.x,Out.y,Out.z);
	D3DXVec3Transform(&Out,&m_RightW,&m_World);
	RightW=D3DXVECTOR3(Out.x,Out.y,Out.z);

	// Set up our view matrix. A view matrix can be defined given an eye point,
    // a point to lookat, and a direction for which way is up.
	// Keep camera's axes orthogonal to each other and of unit length.
	D3DXVec3Normalize(&LookW, &LookW);

	D3DXVec3Cross(&UpW, &LookW, &RightW);
	D3DXVec3Normalize(&UpW, &UpW);

	D3DXVec3Cross(&RightW, &UpW, &LookW);
	D3DXVec3Normalize(&RightW, &RightW);

    D3DXMatrixLookAtLH( &m_matView, &PosW, &(LookW+PosW), &UpW );
	
}
/**
* Construct the projection matrix for this camera.
*/ 
 void GraphCamera::BuildProjectionMatrix()
{
	//limit FOV
	if(m_FOV>D3DX_PI/5*4.0f) m_FOV = static_cast<float>(D3DX_PI/5*4.0f) ;
	if(m_FOV<D3DX_PI/30.0f)m_FOV= static_cast<float>(D3DX_PI/30.0f);

	if(IsOrthoGraph)
	{
		D3DXMatrixOrthoLH(&m_matProj, m_ClientWidth, m_ClientHeight, m_NearClippingPlane, m_FarClippingPlane);
	} else {
		D3DXMatrixPerspectiveFovLH(&m_matProj, m_FOV, m_AspectRatio, m_NearClippingPlane, m_FarClippingPlane);
	}
	//if(IsOrthoGraph)
	//{
	//	m_matViewProj = m_matView * m_matProj * CalculateCropMatrix();
	//} else {
		m_matViewProj = m_matView * m_matProj;
	//}
}
void GraphCamera::SetOrthographicView(bool orthographic, GraphCamera* graphcamera)
{
	m_GraphCamera = graphcamera;
	IsOrthoGraph = orthographic;
	BuildProjectionMatrix();
}
void GraphCamera::IsActive(bool bIsActive)
{
	m_bIsActive = bIsActive;
}

 void GraphCamera::OnResize(int clientWidth, int clientHeight)
{
	//Use new window client window dimensions to calc new aspect ratio
	float Aspect = (float)clientWidth /clientHeight;
	SetAspectRatio(Aspect);
}

  void GraphCamera::Move(D3DXVECTOR3 newPos, bool rotate,float pitch,float yAngle){
	m_PosW.x = newPos.x;
	m_PosW.y = newPos.y;
	m_PosW.z = newPos.z;
	if ( rotate )
	{
		// Rotate camera's look and up vectors around the camera's right vector.
		// R is resulting matrix, second param is axis to turn around, pitch is angle
		D3DXMATRIX R;
		D3DXMatrixRotationAxis(&R, &m_RightW, pitch);
		D3DXVec3TransformCoord(&m_LookW, &m_LookW, &R);
		D3DXVec3TransformCoord(&m_UpW, &m_UpW, &R);


		// Rotate camera axes about the world's y-axis.
		D3DXMatrixRotationY(&R, yAngle);
		D3DXVec3TransformCoord(&m_RightW, &m_RightW, &R);
		D3DXVec3TransformCoord(&m_UpW, &m_UpW, &R);
		D3DXVec3TransformCoord(&m_LookW, &m_LookW, &R);
	}
	// Rebuild the view matrix to reflect changes.
	BuildViewMatrix();
	m_matViewProj = m_matView * m_matProj;
}

void GraphCamera::CalculateFarNearPlanePoints(D3DXVECTOR3 pos, D3DXVECTOR3 look, D3DXVECTOR3 right, D3DXVECTOR3 up, float FOV, float AR, float nearplane, float farplane, vector<D3DXVECTOR3> &pointList)
{
	D3DXVECTOR3 Nc = pos + look * 100;
	D3DXVECTOR3 Fc = pos + look * 250;

	float Hnear = nearplane * (FOV * 0.5f);
	float Hfar = farplane * (FOV * 0.5f);

	float Wnear = Hnear * AR;
	float Wfar = Hfar * AR;

	D3DXVECTOR3 punt;

	//Nearplane Point calculations

	punt = Nc - Hnear * up + Wnear * right;
	pointList.push_back(punt);

	punt = Nc + Hnear * up + Wnear * right;
	pointList.push_back(punt);

	punt = Nc + Hnear * up - Wnear * right;
	pointList.push_back(punt);

	punt = Nc - Hnear * up - Wnear * right;
	pointList.push_back(punt);

	//Farplane Point calculations
	punt = Fc - Hfar * up + Wfar * right;
	pointList.push_back(punt);

	punt = Fc + Hfar * up + Wfar * right;
	pointList.push_back(punt);

	punt = Fc + Hfar * up - Wfar * right;
	pointList.push_back(punt);

	punt = Fc - Hfar* up - Wfar * right;
	pointList.push_back(punt);
}
void GraphCamera::CalculateFrustum()
{

	D3DXMATRIX viewProjection;
    D3DXMatrixMultiply( &viewProjection, &m_matView, &m_matProj );
    //D3DXMatrixMultiply( &viewProjection, &m_mView, &m_mProjection );
    // Left plane
    m_Frustum[0].a = viewProjection._14 + viewProjection._11;
    m_Frustum[0].b = viewProjection._24 + viewProjection._21;
    m_Frustum[0].c = viewProjection._34 + viewProjection._31;
    m_Frustum[0].d = viewProjection._44 + viewProjection._41;
 
    // Right plane
    m_Frustum[1].a = viewProjection._14 - viewProjection._11;
    m_Frustum[1].b = viewProjection._24 - viewProjection._21;
    m_Frustum[1].c = viewProjection._34 - viewProjection._31;
    m_Frustum[1].d = viewProjection._44 - viewProjection._41;
 
    // Top plane
    m_Frustum[2].a = viewProjection._14 - viewProjection._12;
    m_Frustum[2].b = viewProjection._24 - viewProjection._22;
    m_Frustum[2].c = viewProjection._34 - viewProjection._32;
    m_Frustum[2].d = viewProjection._44 - viewProjection._42;
 
    // Bottom plane
    m_Frustum[3].a = viewProjection._14 + viewProjection._12;
    m_Frustum[3].b = viewProjection._24 + viewProjection._22;
    m_Frustum[3].c = viewProjection._34 + viewProjection._32;
    m_Frustum[3].d = viewProjection._44 + viewProjection._42;
 
    // Near plane
    m_Frustum[4].a = viewProjection._13;
    m_Frustum[4].b = viewProjection._23;
    m_Frustum[4].c = viewProjection._33;
    m_Frustum[4].d = viewProjection._43;
 
    // Far plane
    m_Frustum[5].a = viewProjection._14 - viewProjection._13;
    m_Frustum[5].b = viewProjection._24 - viewProjection._23;
    m_Frustum[5].c = viewProjection._34 - viewProjection._33;
    m_Frustum[5].d = viewProjection._44 - viewProjection._43;
 
    // Normalize planes
    for ( int i = 0; i < 6; i++ )
    {
        D3DXPlaneNormalize( &m_Frustum[i], &m_Frustum[i] );
    }
}
bool GraphCamera::CheckIfInFrustum(D3DXVECTOR3 &pos, float radius)
{
	CalculateFrustum();

	for ( int i = 0; i < 6; i++ )
    {
        if ( D3DXPlaneDotCoord( &m_Frustum[i], &pos ) + radius < 0 )
        {
            // Outside the frustum, reject it!
            return false;
        }
    }
    return true;
}
D3DXMATRIX GraphCamera::CalculateCropMatrix()
{
	vector<D3DXVECTOR3> ClippingPoints;


	CalculateFarNearPlanePoints(m_GraphCamera->GetPos(), m_GraphCamera->GetLook(),
		m_GraphCamera->GetRight(), m_GraphCamera->GetUp(),
		m_GraphCamera->GetFOV(), m_GraphCamera->GetAspectRatio(),
		m_GraphCamera->GetNearPlane(), m_GraphCamera->GetFarPlane(),
		ClippingPoints);

	//De punten vermenigvuldigen

	float MaxX = 0;
	float MaxY = 0;
	float MinX = 0;
	float MinY = 1000;
	float MaxZ = 0;
	float MinZ = 1000;

	int Teller = 0;

	m_matViewProj = m_matView * m_matProj;

	BOOST_FOREACH(D3DXVECTOR3 &point, ClippingPoints)
	{
		D3DXVECTOR4 lightVPpoint;
		D3DXVec3Transform(&lightVPpoint, &point, &m_matViewProj);

		lightVPpoint.x /= lightVPpoint.w;
		lightVPpoint.y /= lightVPpoint.w;
		lightVPpoint.z /= lightVPpoint.w;

		if(Teller == 0)
		{
			MaxX = lightVPpoint.x;
			MinX = lightVPpoint.x;
			MaxY = lightVPpoint.y;
			MinY =	lightVPpoint.y;
			MaxZ = lightVPpoint.z;
			MinZ = lightVPpoint.z;
		}
		if(lightVPpoint.x > MaxX)
			MaxX = lightVPpoint.x;
		if(lightVPpoint.y > MaxY)
			MaxY = lightVPpoint.y;
		if(lightVPpoint.x < MinX)
			MinX = lightVPpoint.x;
		if(lightVPpoint.y < MinY)
			MinY = lightVPpoint.y;
		if(lightVPpoint.z > MaxZ)
			MaxZ = lightVPpoint.z;
		if(lightVPpoint.z < MinZ)
			MinZ = lightVPpoint.z;
		//if(point.z < MinZ)
			//MinZ = point.z;
		++Teller;
	}

  // Create the crop matrix  
  float scaleX, scaleY, scaleZ;  
  float offsetX, offsetY, offsetZ;
  scaleX = 2.0f / (MaxX - MinX);  
  scaleY = 2.0f / (MaxY - MinY);  
  offsetX = -0.5f * (MaxX + MinX) * scaleX;  
  offsetY = -0.5f * (MaxY + MinY) * scaleY;  
  scaleZ = 1.0f / (MaxZ - MinZ);  
  offsetZ = -MinZ * scaleZ;

  D3DXMATRIX cropmatrix = D3DXMATRIX( scaleX,     0.0f,     0.0f,  0.0f,  
                   0.0f,   scaleY,     0.0f,  0.0f,  
                   0.0f,     0.0f,   scaleZ,  0.0f,  
                offsetX,  offsetY,  offsetZ,  1.0f);
  
  //m_matViewProj = m_matView * cropmatrix;
  return cropmatrix;
}